package ddproto1.debugger.managing;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.Breakpoint;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;

import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.request.BreakpointRequest;

import ddproto1.GODBasePlugin;
import ddproto1.commons.DebuggerConstants;
import ddproto1.debugger.eventhandler.IEventManager;
import ddproto1.debugger.eventhandler.IProcessingContext;
import ddproto1.debugger.eventhandler.ProcessingContextManager;
import ddproto1.debugger.eventhandler.processors.IJDIEventProcessor;
import ddproto1.debugger.request.DeferrableBreakpointRequest;
import ddproto1.debugger.request.IDeferrableRequest;
import ddproto1.debugger.request.IResolutionListener;
import ddproto1.util.MessageHandler;
import ddproto1.util.traits.JDIEventProcessorTrait;
import ddproto1.util.traits.JDIEventProcessorTrait.JDIEventProcessorTraitImplementor;

public class JavaBreakpoint extends Breakpoint implements IResolutionListener, JDIEventProcessorTraitImplementor {

    private static final Logger logger = MessageHandler.getInstance().getLogger(JavaBreakpoint.class);
    private static final IVMManagerFactory vmmf = VMManagerFactory.getInstance();
    
	private volatile String typeName;
	private volatile int    line;
    
	/** Caches a local map of targets by name. */
    private final Map <String, IJavaDebugTarget> targetsByName = 
    	new HashMap<String, IJavaDebugTarget>();
    
    /** Keeps a map of breakpoint requests to targets in order to efficiently
     * dispatch breakpoint events to their corresponding threads.
     */
    private final Map<BreakpointRequest, IJavaDebugTarget> targetByRequest = 
    		new HashMap<BreakpointRequest, IJavaDebugTarget>();
    
    /** Keeps a map of deferrable breakpoint request by target. There should
     * be a single request for each target.
     */
    private final Map<IJavaDebugTarget, DeferrableBreakpointRequest> deferrableByTarget = 
    		new HashMap<IJavaDebugTarget, DeferrableBreakpointRequest>();
    
    private final JDIEventProcessorTrait jdiProcessorTrait = new JDIEventProcessorTrait(this);
    
    private volatile IJavaBreakpoint associated;
    
    /** State required by the jdi processor trait. */
    private volatile boolean enabled = true;
    private IJDIEventProcessor next;
    
    public JavaBreakpoint(String typeName, int line, IJavaBreakpoint jlb) {
        this.typeName = typeName;
        this.line = line;
        this.associated = jlb;
    }
    	
	public String getModelIdentifier() {
		return DebuggerConstants.PLUGIN_ID;
	}
    
    public IJavaBreakpoint getMappedBreakpoint(){
        return associated;
    }
	
	public void addToTarget(IJavaDebugTarget target)
		throws DebugException
	{
        try{
            IJavaNodeManager vmm = target.getVMManager();
            DeferrableBreakpointRequest dbr = 
                new DeferrableBreakpointRequest(vmm.getName(), typeName, line);
            
            /** We want to hear of all requests generated by this deferrable request. */
            dbr.addResolutionListener(this);
            vmm.getDeferrableRequestQueue().addEagerlyResolve(dbr);

            /** Will be a voter for thread resuming under this target's processing
             * process. 
             */
            	vmm.getVotingManager().declareVoterFor(IEventManager.RESUME_SET);
            
            synchronized (targetByRequest) {
				setDeferrableRequest(target, dbr);
				registerTarget(target);
			}
            
        }catch(Exception ex){
            logger.error("Failed to place breakpoint request.", ex);
            cancelForTarget(target);
            return;
        }
	}

    /** Called whenever a deferrable request is fulfilled. 
     * Request won't be enabled before we return. */
    public void notifyResolution(IDeferrableRequest source, Object byproduct) {
		if (!(source instanceof DeferrableBreakpointRequest)
				|| !(byproduct instanceof BreakpointRequest))
			throw new InternalError("Unexpected request type.");

		DeferrableBreakpointRequest dbr = (DeferrableBreakpointRequest) source;
		BreakpointRequest theRequest = (BreakpointRequest) byproduct;

		/**
		 * This is to avoid the breakpoint being cancelled for a target that's
		 * having a new request added. It could result in garbage being left in
		 * the targetByRequest map.
		 */
		synchronized (targetByRequest) {
			IJavaDebugTarget target = this.getTarget(dbr.getVMID());

			/**
			 * Null target means that the breakpoint is in process of being
			 * removed for this target. This should be a rare case.
			 * 
			 * We can't let this request be enabled, so we re-cancel the
			 * deferrable request.
			 */
			if (target == null) {
				IJavaNodeManager vmm = (IJavaNodeManager)vmmf.getNodeManager(dbr.getVMID()).getAdapter(IJavaNodeManager.class);
				try {
					vmm.getDeferrableRequestQueue().removeRequest(dbr);
				} catch (Exception ex) {
					logger.error(ex);
					throw new RuntimeException("Failed to cancel deferrable "
							+ "breakpoint request for target " + target, ex);
				}
			}

			IJavaNodeManager vmm = target.getVMManager();
			vmm.getEventManager().addEventListener(theRequest,
					jdiProcessorTrait);

			this.addRequestForTarget(theRequest, target);
		}
	}
    
    /**
	 * Specialized process - called whenever one of our event requests is
	 * fulfilled (i.e. whenever one of our breakpoints is hit).
	 */
	public void specializedProcess(Event event) {
	    BreakpointEvent bevt = (BreakpointEvent)event;
		
		IJavaNodeManager _target = (IJavaNodeManager)vmmf.getNodeManager(vmmf
				.getGidByVM(event.request().virtualMachine())).getAdapter(IJavaNodeManager.class);
		String targetName = _target.getName();
		IJavaDebugTarget target = this.getTarget(targetName);
		
		/** Request for cancelled breakpoint. Votes for resuming.*/
		if(target == null){
			IProcessingContext ipc = ProcessingContextManager.getInstance()
					.getProcessingContext();
			ipc.vote(IEventManager.RESUME_SET);
			return; 
		}
		
		/** Gets the thread that was halted by this breakpoint */
		JavaThread thread = target.getVMManager().getThreadManager().getLIThread(
				bevt.thread());
        
        thread.handleBreakpointHit(this);
	}
    
    public void cancelForTarget(IJavaDebugTarget target) throws DebugException {
		synchronized (targetByRequest) {
			/** Removes all requests for this target. */
			if (!isRegistered(target))
				GODBasePlugin.throwDebugException("Invalid target.");
			unregisterTarget(target);
		}
	}
    
    protected void unregisterTarget(IJavaDebugTarget target)
			throws DebugException {
		/**
		 * After this synchronized block completes, this JavaBreakpoint instance
		 * will no longer recognize the target 'target' as a valid target.
		 */

		DeferrableBreakpointRequest theRequest = null;

		synchronized (targetByRequest) {
			Iterator<BreakpointRequest> it = targetByRequest.keySet()
					.iterator();

			while (it.hasNext()) {
				IJavaDebugTarget jdtarget = targetByRequest.get(it.next());
				assert jdtarget != null;
				if (jdtarget.equals(target))
					it.remove();
			}

			targetsByName.remove(target.getVMManager().getName());
			theRequest = deferrableByTarget.remove(target);
			assert theRequest != null;
		}

		/**
		 * We now attempt to cancel the request associated with this target. It
		 * might have already been cancelled at method notifyResolution()
		 * because we released the lock.
		 */
		try {
			target.getVMManager().getDeferrableRequestQueue().removeRequest(
					theRequest);
		} catch (Exception ex) {
			logger.error(ex);
            GODBasePlugin.throwDebugExceptionWithError("Failed to cancel deferrable "
					+ "breakpoint request for target " + target, ex);
		}
	}
    
    protected void registerTarget(IJavaDebugTarget target){
        targetsByName.put(target.getVMManager().getName(), target);
    }
    
    protected IJavaDebugTarget getTarget(String targetName){
        synchronized(targetsByName){
            return targetsByName.get(targetName);
        }
    }
    
    protected boolean isRegistered(IJavaDebugTarget target){
    		return this.targetsByName.containsKey(target.getVMManager().getName());
    }

	public synchronized void next(IJDIEventProcessor next) {
		this.next = next;
	}

	public synchronized IJDIEventProcessor next() {
		return next;
	}

	public boolean enabled() {
		return enabled;
	}

	public void enabled(boolean newValue) {
		enabled = newValue;
	}
	
	protected void addRequestForTarget(BreakpointRequest theRequest, IJavaDebugTarget target){
		synchronized(targetByRequest){
			targetByRequest.put(theRequest, target);
		}
	}
	
	protected void setDeferrableRequest(IJavaDebugTarget target, DeferrableBreakpointRequest dbr){
		this.deferrableByTarget.put(target, dbr);
	}

}
